package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/***
 * 自定义切面，实现公共字段自动填充逻辑
 */

//用于表示这个类是一个切面
@Aspect

//表示这个注解标注的类是可以作为一个功能块（bean）的
@Component

//表示类似于log日志一样的记录操作信息之类的信息
@Slf4j


public class AutoFillAspect {

    /***
     * 切入点
     */
//    就是表示如果同时满足两个条件（：一，在com.sky.mapper这个包下面的方法。二，这
//    个方法有autofill注解。）的时候就会执行所注注解的代码的方法
    @Pointcut("execution(* com.sky.mapper.*.*(..))&&@annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

//    这一句代码表示在执行小括号里面的方法之前会提前执行
//    这个before注解所标注的方法
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");
//        上面是用来检测是否真的被捕获到，用于写实际功能之前的代码调试
//        下面就是定义切面的流程

//        一，获取到当前被拦截的方法上的数据库的操作类型
//        这一句代码就是拦截这个方法的相关信息，然后根据这些信息执行下面代码的判断或者操
        MethodSignature signature=(MethodSignature) joinPoint.getSignature();

//        就是说这两句代码就是判断这个方法有没有autofill ，然后根据autofill后面
//        的value信息进行不同操作的填充比方说insert或者update
        AutoFill autoFill=signature.getMethod().getAnnotation(AutoFill.class);
        OperationType operationType=autoFill.value();

//        二，获取到当前被拦截的方法的参数--实体对象
//        这一段代码就是说明的是把传入这个方法的对象提取出来

        Object args[] = joinPoint.getArgs();
        if (args ==null || args.length==0){
            return;
        }

        Object entity =args[0];

//       三，准备赋值的数据
        LocalDateTime now =LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

//        四，根据当前不同的操作类型，为对应的属性通过反射来赋值

//        判断这个方法是属于哪个类型然后就会执行对应哪个类型的操作
        if (operationType == OperationType.INSERT){

            try{

//                就是说这一段代码就是在此处声明一个方法，然后这个方法就
//                是由后面的getclass之类的找到
//                已经声明过的方法并且用这个新声明的方法代
//                指那个已经定义了的方法。这里算是根据对象（entity）
//                查找他可以用的，他有的方法进行绑定了。找到了才绑定

                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

//                通过反射为对象属性赋值
//                就是说这一段代码就是在此处声明一个方法，然后这个方法就
//                是由后面的getclass之类的找到已经声明过的方法并
//                且用这个新声明的方法代指那个已经定义了的方法

                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);

            } catch (Exception e) {
                e.printStackTrace();
            }


        } else if (operationType ==OperationType.UPDATE) {

            try{

                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

//                通过反射为对象属性赋值

                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }


}
